#==================================================
#	Parse Script
#==================================================
#  解析脚本代码
# * 解析脚本中的方法数据: parse_script_method()
# * 解析脚本中的属性数据: parse_script_variable()
#==================================================
# @datetime: 2021-9-1 15:13:26
#==================================================

extends Reference


var _file = File.new()
var _parse_method = ParseMethod.new()
var _parse_variable = ParseVariable.new()



#==================================================
#   私有方法
#==================================================
## 将参数转换为最终的代码字符串
## @script_or_path_or_code  脚本/路径/代码
## @return  返回代码内容
func _arg_to_code(script_or_path_or_code) -> String:
	var code : String = ""
	
	# 脚本类型
	if script_or_path_or_code is Script:
		code = script_or_path_or_code.source_code
	
	# 字符串类型
	elif script_or_path_or_code is String:
		# 文件
		if _file.file_exists(script_or_path_or_code):
			_file.open(script_or_path_or_code, File.READ)
			code = _file.get_as_text()
		# 字符串
		else:
			code = script_or_path_or_code
		_file.close()
	
	# 其他类型
	else:
		print("[参数错误] 参数值必须是 <Script/String> 类型")
	return code



#==================================================
#   公共方法
#==================================================
##  获取脚本扩展类型
## @script_or_path_or_code  脚本/脚本路径/代码
## @return  返回继承类型或继承的脚本路径
func get_script_extend_type(script_or_path_or_code) -> String:
	var code : String = _arg_to_code(script_or_path_or_code)
	# 开始正则匹配
	var regex = RegEx.new()
	var pattern = "extends\\s+(?<type>\\S+)\n"
	regex.compile(pattern)
	
	var result = regex.search(code)
	if result:
		return result.get_string("type")
	else:
		return ""


##  获取脚本继承链
## @script_or_path  脚本/路径
## @return  返回 脚本以及被继承的脚本 所继承的路径和类型的列表
##    最后一个为最终继承的类型
func get_script_extend_type_link(script_or_path) -> Array:
	var last_type : String
	if script_or_path is String:
		last_type = (load(script_or_path) as Script).resource_path
	elif script_or_path is Script:
		last_type = script_or_path.resource_path
	else:
		printerr("[参数错误] 参数值必须是 <Script/String> 类型")
		return []
	
	var list := []
	while true:
		var type := get_script_extend_type(last_type)
		# 去除继承的脚本的左右字符串，获得继承的脚本路径
		var is_script_path := false
		if type.left(1) == "\"":
			type = type.trim_prefix("\"").trim_suffix("\"")
			is_script_path = true
		elif type.left(1) == "'":
			type = type.trim_prefix("'").trim_suffix("'")
			is_script_path = true
		# 是脚本路径
		if is_script_path:
			var path = _relative_convert_absolute_path(last_type, type)	# 相对路径转为绝对路径
			type = path
			list.push_back(path)
		else:
			list.push_back(type)
			break
		last_type = type
	return list


##  获取每行脚本代码
## @script_or_path  脚本/脚本路径/代码
## @return  返回脚本每行代码
func get_script_line_code(script_or_path_or_code) -> Array:
	var code := _arg_to_code(script_or_path_or_code) as String
	return code.split("\n") as Array


##  解析脚本方法
## @script_or_path_or_code  脚本/路径/代码
## @return  返回解析后的脚本中的方法的数据
##    每个数据项的 key 为： 
##        code 匹配的方法行代码、 name 方法名、 args 参数列表信息
##        is_static 是否为静态类型、 line 方法所在行
func parse_script_method(script_or_path_or_code) -> Array:
	var code := _arg_to_code(script_or_path_or_code)
	return _parse_method.parse(code)


##  解析脚本变量
## @script_or_path_or_code  脚本/路径/代码
## @return  返回解析后的脚本中的属性的数据
##    每个数据项的 key 为： 
##        code 匹配的属性行代码、 name 属性名、 
##        value 属性值、 type 属性类型、 setget setget及后缀方法名、
##        hint export中的导出标识、 comments 注释、 line 变量所在行
func parse_script_variable(script_or_path_or_code) -> Array:
	var code := _arg_to_code(script_or_path_or_code)
	return _parse_variable.parse(code)


##  对脚本中的字典变量插入数据（只能对非{}嵌套的变量有效）
## @script_path  脚本路径
## @variable_name  变量名
## @key_value_list  键值对列表。参数格式：[key, value, key, value]
## @return  返回插入后的新的代码
func dict_insert_data(
	script_or_path_or_code, 
	variable_name: String,
	key_value_list: Array
) -> String:
	var ENTER_KEY = "¶"
	
	# 获取脚本代码
	var code := _arg_to_code(script_or_path_or_code)
	code = code.replace("\n", ENTER_KEY)
	
	# 正则
	var regex = RegEx.new()
	var pattern = (
		"(const|var|onready\\s+var)[\\s{enter}]+"
		+ variable_name + "\\s*=\\s*\\{"
		+ ".*?"		# 匹配列表内容
		+"{enter}\\}\\s*?{enter}"
	).format({
		"enter": ENTER_KEY
	})
	regex.compile(pattern)
	
	# 搜索匹配
	var result = regex.search(code)
	if result:
		for idx in range(key_value_list.size()/2):
			var key = key_value_list[idx * 2]
			var value = key_value_list[idx * 2 + 1]
			code = code.insert(
				result.get_end() - 3, 
				'\t' + key + ' = ' + value + ',\n'
			)
	else:
		print("[未匹配到变量] 代码未进行更改")
	
	code = code.replace(ENTER_KEY, "\n")
	return code


##  相对路径转换为绝对路径
## @script_path  脚本路径（绝对路径）
## @relative_path  继承的相对路径
## @return  返回绝对路径
func _relative_convert_absolute_path(
	script_path: String, 
	relative_path: String
) -> String:
	# 判断是否为绝对路径，如果是，则返回这个路径
	if _file.file_exists(relative_path):
		return relative_path
	# 返回绝对路径
	return FilePath.get_absolute_path(script_path, relative_path)



#==================================================
#   内部类
#==================================================
## 解析代码中的方法
class ParseMethod extends Reference:
	var method_regex : RegEx
	var arg_regex : RegEx
	
	func _init() -> void:
		# 匹配方法
		method_regex = RegEx.new()
		method_regex.compile(
			# 开头
			"((?<is_static>static)\\s+func|func)\\s+"
			# 方法名
			+ "(?<name>\\S+)\\s*"
			# 参数列表
			+ "\\(" 
				+ "(?<args>[^)]+)?"
			+ "\\)\\s*"
			# 返回类型
			+ "(->\\s*(?<type>\\w+):|:)"
		)
		
		# 匹配参数
		arg_regex = RegEx.new()
		arg_regex.compile(
			# 参数名称
			"(?<name>\\w+)\\s*"
			# 类型
			+ "(\\s*:\\s*(?<type>\\w+)?)?"
			# 默认值
			+ "(\\s*=\\s*(?<value>[^,#]+))?"
		)
	
	## 开始解析
	## @code_list 代码列表
	func parse(code: String) -> Array:
		var data_list = []
		# 开始搜索匹配
		var result_list = method_regex.search_all(code)
		# 整理匹配结果
		for result in result_list:
			result = result as RegExMatch
			var data = {
				code = result.get_string(),	# 匹配到的代码
				name = result.get_string("name"),	# 方法名
				type = result.get_string("type"),	# 返回类型
				args = _parse_args(result.get_string("args")),	# 参数列表
				static = result.get_string("is_static") != "",	# 是否为静态类型方法
			}
			
			# 方法代码所在行
			var start_pos = result.get_start()
			data["line"] = code.left(start_pos).split("\n").size() - 1
			data_list.push_back(data)
		return data_list
	
	##  解析参数
	func _parse_args(code: String) -> Array:
		var list = []
		var result_list = arg_regex.search_all(code)
		for result in result_list:
			list.push_back({
				name = result.get_string("name"), # 参数名
				type = result.get_string("type"), # 类型
				default = result.get_string("value").strip_edges() # 默认值
			})
		return list


## 解析代码中的变量
class ParseVariable extends Reference:
	var variable_regex : RegEx
	
	func _init() -> void:
		variable_regex = RegEx.new()
		var pattern = (
			"^(?<code>"
				# 判断开头
				+ "("
					# export 开头
					+ "export\\s*"
					+ "(?<hint>\\([^\\)]+\\)\\s*)?" # hint 导出标识
					+ "var\\s+"		# var
					
					# 或 onready 开头
					+ "|onready\\s+"
					+ "var\\s+?"		# var
					
					# 或 var 开头
					+ "|var\\s+"
				+ ")"
				
				+ "(?<name>\\S+)\\s*?"	# name 方法名
				+ "(\\s*:\\s*)?"		# : 冒号
				+ "(?<type>\\w+)?"		# type 类型
				
				# 变量值
				+ "(\\s*=\\s*"	# 等号
					+ "(?<value>[^#\n\\s]+)?"	# 直到不是 # 或者回车的位置
				+ ")?"
				
				# setget
				+ "(?<setget>\\s*setget\\s+(\\w+)?(\\s*,\\s*\\w+)?)?"
			
			+ ")"	# 代码
			
			+ "(?<comments>\\s*#[^\n]*)?"	# 注释
		)
		variable_regex.compile(pattern)
	
	##  解析属性
	## @return  返回代码解析后的数据
	func parse(code: String) -> Array:
		var code_list = code.split("\n")
		var text = ""
		var data_list = []
		for line in code_list.size():
			text = code_list[line]
			var r_match : RegExMatch = variable_regex.search(text)
			if r_match:
				var data = {
					code = r_match.get_string("code"),
					name = r_match.get_string("name"),
					value = r_match.get_string("value"),
					type = r_match.get_string("type"),
					setget = r_match.get_string("setget"),
					hint = r_match.get_string("hint"),
					comments = r_match.get_string("comments"),
					line = line,
				}
				data_list.push_back(data)
		
		return data_list


## 将相对路径转为绝对路径
class FilePath extends Reference:
	
	## 获取绝对路径
	static func get_absolute_path(current_path: String, relative_path: String) -> String:
		var file = relative_path.get_file()
		# 当前文件转为路径
		if current_path.right(current_path.length() - 1) != "/":
			current_path = current_path.get_base_dir()
		# 当前目录
		if relative_path.left(2) == "./":
			relative_path = current_path + "/" + relative_path.right(2)
		# 上级目录个数
		var p_dir_count := relative_path.count("../") as int
		var parent_dir := current_path as String
		for i in p_dir_count:
			parent_dir = get_parent_dir(parent_dir)
		return parent_dir + "/" + file
	
	## 获取父节点目录
	static func get_parent_dir(dir: String) -> String:
		if dir.right(dir.length()-1) == "/":
			dir = dir.left(dir.length()-1)
		return dir.left(dir.find_last("/"))


